﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Globalization;
using System.Linq;
using System.Security;
using System.Web;
using MetaCollection.ContentService.Http;
using MetaCollection.ContentService.WebDav;
using MetaCollection.Framework;
using MetaCollection.Framework.Model;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;
using MetaCollection.Framework.Security;

namespace MetaCollection.ContentService
{
	public class ContentEndpoint
	{
		/// <summary>
		/// Request handler
		/// </summary>
		private IRequestHandler m_handler;
		/// <summary>
		/// Supported Methods.
		/// </summary>
		private HttpMethods m_SupportedMethods = HttpMethods.None;

		internal IRequestHandler Handler
		{
			get { return m_handler; }
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ContentEndpoint"/> class.
		/// </summary>
		/// <param name="handler">The handler.</param>
		/// <param name="supportedMethods">The supported methods.</param>
		public ContentEndpoint(IRequestHandler handler, HttpMethods supportedMethods)
		{
			m_handler = handler;
			m_SupportedMethods = supportedMethods;
		}

		/// <summary>
		/// Determines whether [is valid path] [the specified context].
		/// </summary>
		/// <returns>
		/// 	<c>true</c> if [is valid path] [the specified context]; otherwise, <c>false</c>.
		/// </returns>
		internal bool IsValidPath(HttpContext context)
		{
			try
			{
				Uri itemUri = context.Request.Url.ToCollectionUri();
				IItemInstance requestItem = GetRequestedItem(context, itemUri, context.Request["ver"]);

				if (requestItem != null)
				{
					if (requestItem.MimeType.Equals("application/x-redirect", StringComparison.OrdinalIgnoreCase))
						ProcessRedirect(context, requestItem);
					else
						context.Items.Add("RequestItem", requestItem);			// requested item
					return true;
				}
				else if (context.Request.Method().IsSet(HttpMethods.Put) ||
					context.Request.Method().IsSet(HttpMethods.Lock) ||
					context.Request.Method().IsSet(HttpMethods.MKCol) ||
					context.Request.Method().IsSet(HttpMethods.Delete))
				{
					// some methods do not require the item to exist before taking action... for those methods return true even though the item is not found.
					// other methods (like Delete) we may not find if they are not published, but we want to handle them anyway
					return true;
				}
			}
			catch (SecurityException sec)
			{
				context.Response.Clear();
				context.Response.StatusCode = 401;
				context.Response.SubStatusCode = (int)Http.SubStatus401.UnauthorizedByAcl;
				HttpException hex = new HttpException(401, sec.Message, sec);
				context.Response.Write(hex.GetHtmlErrorMessage());
				context.Response.End();
				return true;
			}
			return false;
		}

		/// <summary>
		/// Processes an item that is a redirect.  Outputs the redirect location and HTTP headers
		/// to send the client to the configured destination.
		/// </summary>
		/// <param name="requestItem">The request item.</param>
		private static void ProcessRedirect(HttpContext context, IItemInstance requestItem)
		{
			IItemProperty location = requestItem.Properties.Where(p => p.KeyName.Equals("ContentService.Location", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
			if (location != null)
			{
				context.Response.Clear();
				context.Response.StatusCode = (int)Http.RedirectStatus.MovedPermanently;
				context.Response.Headers.Add("Location", location.KeyValue);
				context.Response.End();
			}
		}

		/// <summary>
		/// Gets the requested item.
		/// </summary>
		/// <param name="itemUri">The item URI.</param>
		/// <param name="version">The version request parameter.</param>
		/// <returns>an item instance for the given itemUri.</returns>
		/// <remarks>This method evaluates the version parameter (ver) as appropriate for version specific requests.</remarks>
		public IItemInstance GetRequestedItem(HttpContext context, Uri itemUri, string version)
		{
			IItemInstance requestItem;
			if (itemUri == null)
				throw new ArgumentNullException("itemUri");
			// TODO : itemUri for encoded ('/content/' or '/content/1000,00,00.jpg' or '/content/00000000-0000-0000-0000-000000000000,00,00.jpg'
			// process these uris using an item search instead of a getitem request.  log a warning if there are multiple items found, process versions as normal.

			// '/content/1000,00,00.jpg' == search for item by external id, include additional values in the request context for MaxWidth, MaxHeight image constraints.  These will be used by the getitemcontroller for image sizing.
			// '/content/00000000-0000-0000-0000-000000000000' == search by item id... if ,00,00 is included then add max width, height data as above.
			// '/content/' all other requests on the content folder should be mapped to the root of the website. (allow this entry to be disabled through configuration!!)

			if (itemUri.Segments.Contains("content/", StringComparer.OrdinalIgnoreCase))
			{
				string lastSegment = itemUri.Segments[itemUri.Segments.Count() - 1].Split('?')[0]; 

				Uri targetUri = new Uri((string.Format(CultureInfo.InstalledUICulture, "col://{0}", itemUri.Host)));
				if (lastSegment.Contains("-"))
				{
					// '/content/00000000-0000-0000-0000-000000000000' == search by item id... if ,00,00 is included then add max width, height data as above.
					targetUri = new Uri(string.Format(CultureInfo.InstalledUICulture, "col://{0}/{1}", itemUri.Host, lastSegment.Split(',')[0]));
				}
				else if (lastSegment.Contains(","))
				{
					// '/content/1000,00,00.jpg' == search for item by external id, include additional values in the request context for MaxWidth, MaxHeight image constraints.  These will be used by the getitemcontroller for image sizing.
					Lucene.Net.Index.Term term = new Lucene.Net.Index.Term("ExternalId", lastSegment.Split(',')[0]);
					Lucene.Net.Search.TermQuery query = new TermQuery(term);
					var searcher = RequestHandlerFactory.CreateIndexSearcher();
					var results = searcher.Search(query, null, 1).scoreDocs;
					Lucene.Net.Documents.Document hit = searcher.Doc((results[0] as ScoreDoc).doc);
					if (hit != null)
					{
						targetUri = new Uri(hit.Get("Href"));
					}
				}
				else
				{
					// '/content/' all other requests on the content folder should be mapped to the root of the website. (allow this entry to be disabled through configuration!!)
					targetUri = new Uri(itemUri.ToString().Replace("/content", ""));
				}
				itemUri = targetUri;
			}

			// no version provided gives default behavior of published otherwise it is item.version = (latest | ##)
			// if the version control method is not set then we will only support returning the published version of an item.
			if ((!m_SupportedMethods.IsSet(HttpMethods.VersionControl)) || (string.IsNullOrEmpty(version)))
				requestItem = Handler.ItemRequest(context.User as MetaPrincipal, RequestTypes.GetItem | RequestTypes.VersionPublished, itemUri).FirstOrDefault();
			else if (version.Equals("latest", StringComparison.OrdinalIgnoreCase))
				requestItem = Handler.ItemRequest(context.User as MetaPrincipal, RequestTypes.GetItem | RequestTypes.VersionLatest, itemUri).FirstOrDefault();
			else
				requestItem = Handler.ItemRequest(context.User as MetaPrincipal, RequestTypes.GetItem | RequestTypes.VersionSpecific, itemUri, (int?)Convert.ToInt32(version, CultureInfo.CurrentCulture)).FirstOrDefault();

			return requestItem;
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		public void Dispose()
		{
			m_handler.Dispose();
		}


	}
}
